#include "RWLock.h"
#include "Mutex.h"
#include <limits.h>
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

using namespace std;

/* Selecciona el test a correr */
#define TEST_LECTOR lector 		// Cambiar por lector_0 o lector_1 para los distintos tests
#define TEST_ESCRITOR escritor	// Cambiar por escritor_0 o escritor_1 para los distintos tests

/* Cantidad de threads por lote */
#define MAX_THREADS 131070

#define MAX_LECTURAS 20000				// Ajustar este número según el sistema

#define MAX_OPERACIONES_ESPUREAS 100	// Ajustar este número según el sistema

#define PROBABILIDAD_DE_LECTORES 75

/* Estructura para guardar cada lectura */
struct log_entry {
	int id;
	char rw;	// Si es un lector o un escritor
	int valor;	// Valor leído
};

/* Arreglo con la salida */
log_entry log[MAX_THREADS];

RWLock* rwl = new RWLock();

/* La variable compartida */
int pizarron;

/*
 * Nuestra prueba de estrés...
 */

int lote_de_operaciones_espureas() {
	int i;

	while (i < MAX_OPERACIONES_ESPUREAS) {
		++i;
	}

	return 0;
}

void* lector(void* p) {
	int* valor = (int*) p, val = 0, aux, i;
	*valor = 0;

	/* Entra a la zona crítica */
	rwl->rlock();

	/* Lee la variable compartida */
	aux = pizarron;
	//cerr << "L: " << aux << endl;

	/* Ejecuta algunas operaciones espureas */
	lote_de_operaciones_espureas();

	for (i = 0; i < MAX_LECTURAS; ++i) {
		if (aux != pizarron) {
			val = val + 1;
		}
	}
	*valor = val;

	/* Sale de la zona crítica */
	rwl->runlock();

	return NULL;
}

void* escritor(void* p) {
	int* valor = (int*) p, val = 0, aux, i;
	*valor = 0;

	/* Entra a la zona crítica */
	rwl->wlock();

	/* Lee, escribe, lee la variable compartida */
	aux = ++pizarron;
	//cerr << "E: " << aux << endl;

	/* Ejecuta algunas operaciones espureas */
	lote_de_operaciones_espureas();

	for (i = 0; i < MAX_LECTURAS; ++i) {
		if (aux != pizarron) {
			val = val + 1;
		}
	}
	*valor = val;

	/* Sale de la zona crítica */
	rwl->wunlock();

	return NULL;
}

void* lector_0(void* p) {
	int* valor = (int*) p, val = 0, aux, i;
	*valor = 0;

	/* Entra a la zona crítica */
	rwl->rlock();

	/* Lee la variable compartida */
	aux = pizarron;

	/* Ejecuta algunas operaciones espureas */
	lote_de_operaciones_espureas();

	for (i = 0; i < MAX_LECTURAS; ++i) {
		if (aux != pizarron) {
			val = val + 1;
		}
	}
	*valor = val;

	/* Sale de la zona crítica */
	rwl->runlock();

	return NULL;
}

void* escritor_0(void* p) {
	int* valor = (int*) p, val = 0, i;
	*valor = 0;

	/* Entra a la zona crítica */
	rwl->wlock();

	/* Ejecuta algunas operaciones espureas */
	lote_de_operaciones_espureas();

	for (i = 0; i < MAX_LECTURAS; ++i) {
		++pizarron;
	}
	*valor = val;

	/* Sale de la zona crítica */
	rwl->wunlock();

	return NULL;
}

void* lector_1(void* p) {
	int* valor = (int*) p, val = 0, aux, i;
	*valor = 0;

	/* Entra a la zona crítica */
	rwl->rlock();

	/* Lee la variable compartida */
	aux = pizarron;
	cerr << "L: " << aux << endl;

	/* Ejecuta algunas operaciones espureas */
	lote_de_operaciones_espureas();

	for (i = 0; i < MAX_LECTURAS; ++i) {
		if (aux != pizarron) {
			val = val + 1;
		}
	}
	*valor = val;

	/* Sale de la zona crítica */
	rwl->runlock();

	return NULL;
}

void* escritor_1(void* p) {
	int* valor = (int*) p, val = 0, aux, i;
	*valor = 0;

	/* Entra a la zona crítica */
	rwl->wlock();

	/* Lee, escribe, lee la variable compartida */
	aux = ++pizarron;
	cerr << "E: " << aux << endl;

	/* Ejecuta algunas operaciones espureas */
	lote_de_operaciones_espureas();

	for (i = 0; i < MAX_LECTURAS; ++i) {
		if (aux != pizarron) {
			val = val + 1;
		}
	}
	*valor = val;

	/* Sale de la zona crítica */
	rwl->wunlock();

	return NULL;
}
/* Auxiliar */
int valor_anterior;

void* lector_2(void* p) {

	/* Entra a la zona crítica */
	rwl->rlock();

	/* Lee la variable compartida */
	cout << "L " << pizarron << endl;

	/* Sale de la zona crítica */
	rwl->runlock();

	return NULL;
}

void* escritor_2(void* p) {

	/* Entra a la zona crítica */
	rwl->wlock();

	/* Escribe la variable compartida */
	++pizarron;

	/* Lee la variabla compartida */
	cout << "E " << pizarron << endl;

	/* Sale de la zona crítica */
	rwl->wunlock();

	return NULL;
}

void* lector_3(void* p) {

	/* Entra a la zona crítica */
	rwl->rlock();

	/* Lee la variable compartida */
	assert(pizarron - valor_anterior <= 1);

	/* Sale de la zona crítica */
	rwl->runlock();

	return NULL;
}

void* escritor_3(void* p) {

	/* Entra a la zona crítica */
	rwl->wlock();
	/* Lee la variable compartida */
	assert(pizarron - valor_anterior <= 1);

	/* Guarda el valor anterior */
	valor_anterior = pizarron;

	/* Escribe la variable compartida */
	++pizarron;

	/* Lee la variable compartida */
	assert(pizarron - valor_anterior <= 1);

	/* Sale de la zona crítica */
	rwl->wunlock();

	return NULL;
}

int lectores_en_zona_critica, escritores_en_zona_critica;

Mutex* mutex_lectores = new Mutex();
Mutex* mutex_escritores = new Mutex();

void* lector_4(void* p) {

	/* Entra a la zona crítica */
	rwl->rlock();

	mutex_lectores->lock();
	++lectores_en_zona_critica;
	mutex_lectores->unlock();

	mutex_escritores->lock();
	mutex_lectores->lock();
	cout << "L: L " << lectores_en_zona_critica << " E "
			<< escritores_en_zona_critica << endl;
	assert(escritores_en_zona_critica == 0);
	mutex_lectores->unlock();
	mutex_escritores->unlock();

	mutex_lectores->lock();
	--lectores_en_zona_critica;
	mutex_lectores->unlock();

	/* Sale de la zona crítica */
	rwl->runlock();

	return NULL;
}

void* escritor_4(void* p) {

	/* Entra a la zona crítica */
	rwl->wlock();

	mutex_escritores->lock();
	++escritores_en_zona_critica;
	mutex_escritores->unlock();

	mutex_lectores->lock();
	mutex_escritores->lock();
	assert(lectores_en_zona_critica == 0);
	cout << "E: L " << lectores_en_zona_critica << " E "
			<< escritores_en_zona_critica << endl;
	mutex_escritores->unlock();
	mutex_lectores->unlock();

	mutex_escritores->lock();
	--escritores_en_zona_critica;
	mutex_escritores->unlock();

	/* Sale de la zona crítica */
	rwl->wunlock();

	return NULL;
}

int main(int argc, const char* argv[]) {
	int cantidad_de_lectores, cantidad_de_escritores, total_de_threads, fallos,
			total_de_fallos;	//, ultimo_escritor;

	int i, s, seed, boletos, probabilidad_de_lectores, boleto, err;

	pthread_t thread;

	total_de_fallos = 0;
	for (s = 0; s < 100; ++s) {
		seed = s;

		cantidad_de_lectores = 0;
		cantidad_de_escritores = 0;
		total_de_threads = 0;

		fallos = 0;

		pizarron = 0;
		valor_anterior = 0;

		escritores_en_zona_critica = 0;
		lectores_en_zona_critica = 0;

		boletos = 100;
		probabilidad_de_lectores = PROBABILIDAD_DE_LECTORES;

		/* Inicializa la semilla */
		srand(seed);
		//srand(time(NULL));

		i = 0;
		while (i < MAX_THREADS) {
			/* Sortea si va lanzar un lector o escritor */
			boleto = rand() % boletos;

			/* Según el resultado del sorteo */
			if (boleto < probabilidad_de_lectores) {
				log[i].id = i;
				log[i].rw = 'L';
				log[i].valor = -1;

				/* Lanza un lector */
				err = pthread_create(&thread, NULL, TEST_LECTOR, &log[i].valor);

				if (err != 0) {
					cout << "Error al crear el thread";
				} else {
					++cantidad_de_lectores;
					++total_de_threads;
					++i;
				}

			} else {
				log[i].id = i;
				log[i].rw = 'E';
				log[i].valor = -1;

				/* Lanza un escritor */
				err = pthread_create(&thread, NULL, TEST_ESCRITOR,
						&log[i].valor);

				if (err != 0) {
					cout << "Error al crear el thread";
				} else {
					++cantidad_de_escritores;
					++total_de_threads;
					++i;
				}

			}

			assert(err == 0);

			/* Soltamos los threads */
			pthread_detach(thread);
		}

		/* Revisamos los errores */
		for (i = 0; i < MAX_THREADS - 1; ++i) {
			if (log[i].valor > log[i + 1].valor) {
				fallos = fallos + log[i].valor;
			}
		}

		cout << "Semilla: " << s << endl;
		cout << "Lectores: " << cantidad_de_lectores << endl;
		cout << "Escritores: " << cantidad_de_escritores << endl;
		cout << "Total: " << total_de_threads << endl;
		cout << "Probabilidad: " << 100 - PROBABILIDAD_DE_LECTORES << endl;
		cout << "Tasa de escrituras: "
				<< (float) cantidad_de_escritores / MAX_THREADS * 100 << " %"
				<< endl;
		cout << "Fallos: " << fallos << endl;
		cout << "Tasa de fallos: " << (float) fallos / MAX_THREADS * 100 << " %"
				<< endl;
		cout << endl;

		/* Acumulamos los fallos */
		total_de_fallos = total_de_fallos + fallos;

		cout << "Lotes: " << s << endl;
		cout << "Total de fallos: " << total_de_fallos << endl;
		cout << "Total de escrituras: " << s * MAX_THREADS << endl;
		cout << "Total de lecturas: "
				<< (long) (s + 1) * (long) MAX_THREADS * (long) MAX_LECTURAS
				<< endl;

		cout << "Tasa total de fallos: "
				<< (double) total_de_fallos / (double) (s + 1)
						* (double) MAX_THREADS * 100 << endl;
		cout << endl;

	}
	return 0;
}
